using System;
using System.Diagnostics;
using System.Threading.Tasks;
using PddWebAuto.Helper;
using PuppeteerSharp;
using PuppeteerSharp.Input;

namespace PddWebAuto
{
    public static class IPageEx
    {
        private static readonly Random _Ran_ = new Random();

        private static readonly string Script_React =
            "function cefjs_React(dom) {\r\n  if (dom==null) {\r\n    return null;\r\n  }\r\n  let key = Object.keys(dom).find(key=>key.startsWith(\"__reactInternalInstance$\"));\r\n  let internalInstance = dom[key];\r\n  if (internalInstance == null) return null;\r\n\r\n  if (internalInstance.return) { // react 16+\r\n      return internalInstance._debugOwner\r\n          ? internalInstance._debugOwner.stateNode\r\n          : internalInstance.return.stateNode;\r\n  } else { // react <16\r\n      return internalInstance._currentElement._owner._instance;\r\n  }\r\n};\r\n";

        public static async Task<bool> MyGoToAsync(this IPage p, Process process, string Url, int Timeout = 10 * 1000)
        {
            try
            {
                return await NetworkHelper.LoadAndWaitForAsync(p, process, Url, Timeout);
            }
            catch (Exception)
            {
                // ignored
            }

            return false;
        }

        /// <summary>
        /// </summary>
        /// <param name="p"></param>
        /// <param name="process"></param>
        /// <param name="faild"></param>
        /// <param name="Timeout"></param>
        /// <returns></returns>
        public static async Task MyWaitLoadingCompleted(this IPage p, Process process = null,
            Func<Task> faild = null,
            int Timeout = 3 * 1000)
        {
            try
            {
                await NetworkHelper.WaitForNetworkIdleAsync(p, process, Timeout, faild);
                return;
            }
            catch (Exception)
            {
                // ignored
            }
        }

        /// <summary>
        ///     等待加载时间单位是秒
        /// </summary>
        /// <param name="p"></param>
        /// <param name="Selector"></param>
        /// <param name="Timeout">单位是秒</param>
        /// <returns></returns>
        public static async Task<bool> MyWaitSelector(this IPage p, string Selector, Func<Task> faildAction,
            int Timeout = 6)
        {
            return await NetworkHelper.WaitSelector(p, Selector, Timeout * 10, faildAction);
        }


        public static async Task<bool> MyWaitXPath(this IPage p, string Selector, int Timeout = 6 * 1000)
        {
            try
            {
                var El = await p.WaitForXPathAsync(Selector,
                    new WaitForSelectorOptions { Visible = true, Timeout = Timeout });
                return El != null;
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<IElementHandle> MyGetXPath(this IPage p, string XPath)
        {
            try
            {
                var El = await p.XPathAsync(XPath);
                if (El.Length > 0)
                {
                    return El[0];
                }
            }
            catch (Exception)
            {
            }

            return null;
        }

        public static async Task<IElementHandle> MyGetXPathAndVisible(this IPage p, string XPath)
        {
            try
            {
                var El = await p.XPathAsync(XPath);
                if (El.Length > 0)
                {
                    if (await El[0].IsIntersectingViewportAsync())
                    {
                        return El[0];
                    }
                }
            }
            catch (Exception)
            {
            }

            return null;
        }

        /// <summary>
        ///     获取元素且在视图可视
        /// </summary>
        /// <param name="p"></param>
        /// <param name="Selector"></param>
        /// <returns></returns>
        public static async Task<IElementHandle> MyGetSelectorAndVisible(this IPage p, string Selector)
        {
            try
            {
                var el = await p.QuerySelectorAllAsync(Selector);
                if (el != null && el.Length > 0)
                {
                    foreach (var item in el)
                    {
                        if (await item.IsIntersectingViewportAsync())
                        {
                            return item;
                        }
                    }
                }
            }
            catch (Exception)
            {
            }

            return null;
        }

        public static async Task<bool> MySelectorIsExist(this IPage p, string Selector)
        {
            try
            {
                var el = await p.QuerySelectorAsync(Selector);
                return el != null;
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<bool> MySelectorIsExistAndVisible(this IPage p, string Selector)
        {
            try
            {
                var el = await p.QuerySelectorAsync(Selector);
                if (el != null)
                {
                    return await el.IsIntersectingViewportAsync();
                }
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<bool> MyXPathIsExistAndVisible(this IPage p, string Selector)
        {
            try
            {
                var el = await p.XPathAsync(Selector);
                if (el != null && el.Length > 0)
                {
                    return await el[0].IsIntersectingViewportAsync();
                }
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<bool> MySelectorClick(this IPage p, string Selector)
        {
            try
            {
                var el = await p.QuerySelectorAsync(Selector);
                if (el != null)
                {
                    await p.ClickAsync(Selector, GetClickOptions());
                    return true;
                }
                return false;
            }
            catch (Exception)
            {
                // ignored
            }

            return false;
        }

        /// <summary>
        ///     元素内触发移动事件
        /// </summary>
        /// <param name="p"></param>
        /// <param name="Selector"></param>
        /// <param name="StartX"></param>
        /// <param name="StartY"></param>
        /// <param name="OffsetX"></param>
        /// <returns></returns>
        public static async Task<bool> MySelectorMove(this IPage p, string Selector, int StartX, int StartY)
        {
            try
            {
                var el = await p.QuerySelectorAsync(Selector);
                if (el != null)
                {
                    var Box = await el.BoundingBoxAsync();
                    var X = (int)Box.X + StartX;
                    var Y = (int)Box.Y + StartY;
                    await p.Mouse.MoveAsync(X, Y);
                    await TaskHelper.Delay(100);
                    return true;
                }
            }
            catch (Exception)
            {
            }

            return false;
        }

        /// <summary>
        ///     元素范围内触发滚动
        /// </summary>
        /// <param name="p"></param>
        /// <param name="Selector"></param>
        /// <param name="StartX">元素范围内起始X</param>
        /// <param name="StartY">元素范围内起始Y</param>
        /// <param name="deltaY">滚动Y</param>
        /// <param name="MoveMouse">默认先移动鼠标到位置</param>
        /// <returns></returns>
        public static async Task<bool> MySelectorWheel(this IPage p, string Selector, int StartX, int StartY,
            int deltaY = 100, bool MoveMouse = true)
        {
            try
            {
                var el = await p.QuerySelectorAsync(Selector);
                if (el != null)
                {
                    var Box = await el.BoundingBoxAsync();
                    var X = (int)Box.X + StartX;
                    var Y = (int)Box.Y + StartY;

                    if (MoveMouse)
                    {
                        await p.Mouse.MoveAsync(X, Y);
                        await TaskHelper.Delay(100);
                    }

                    await p.Mouse.WheelAsync(X, deltaY);
                    await TaskHelper.Delay(100);
                    return true;
                }
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<string> MyGetHtml(this IPage p)
        {
            try
            {
                return await p.GetContentAsync();
            }
            catch (Exception)
            {
            }

            return null;
        }

        public static async Task<bool> MyXpathClick(this IPage p, string Xpath)
        {
            try
            {
                var El = await p.MyGetXPath(Xpath);
                if (El != null)
                {
                    return await p.MyClick(El);
                }
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<bool> MyClick(this IPage p, IElementHandle El, int OffX = 0, int OffY = 0)
        {
            try
            {
                var Options = GetClickOptions();
                if (OffX != 0 && OffY != 0)
                {
                    Options.OffSet = new Offset(OffX, OffY);
                }

                await El.ClickAsync(Options);
                return true;
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<bool> MyClick(this IElementHandle El)
        {
            try
            {
                await El.ClickAsync(GetClickOptions());
                return true;
            }
            catch (Exception)
            {
            }

            return false;
        }

        /// <summary>
        ///     点击元素传递 传递 宽度高度 返回坐标
        /// </summary>
        /// <param name="El"></param>
        /// <param name="Fcun">宽度,高度</param>
        /// <returns></returns>
        public static async Task<bool> MyClick(this IElementHandle El, Func<decimal, decimal, Offset> Fcun)
        {
            try
            {
                var Options = GetClickOptions();
                var Box = await El.BoundingBoxAsync();
                if (Box == null)
                {
                    return false;
                }

                var Offset = Fcun.Invoke(Box.Width, Box.Height);
                Options.OffSet = Offset;
                await El.ClickAsync(Options);
                return true;
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<bool> MySelectorInputAndFocusAndRetry(this IPage p, string Selector, string Text,
            bool Empty = false, int RetryNum = 3)
        {
            for (var i = 0; i < RetryNum; i++)
            {
                if (await p.MySelectorInputAndFocus(Selector, Text, Empty))
                {
                    return true;
                }

                await TaskHelper.Delay(1000);
            }

            return false;
        }


        public static async Task<bool> MySelectorInputAndFocus(this IPage p, string Selector, string Text,
            bool Empty = false)
        {
            try
            {
                var El = await p.QuerySelectorAsync(Selector);
                if (El != null)
                {
                    await El.FocusAsync();
                    await TaskHelper.Delay(_Ran_.Next(200, 400));
                    if (Empty)
                    {
                        var VX = 0;
                        while (await p.EvaluateExpressionAsync<int>("document.querySelector(\"" + Selector +
                                                                    "\").value.length") > 0)
                        {
                            if (VX > 5)
                            {
                                return false;
                            }

                            await p.EvaluateExpressionHandleAsync("document.querySelector(\"" + Selector +
                                                                  "\").value=\"\"");
                            await p.TypeAsync(Selector, "");
                            await TaskHelper.Delay(_Ran_.Next(400, 800));
                            VX++;
                        }
                    }

                    await TaskHelper.Delay(_Ran_.Next(100, 200));
                    await p.Keyboard.TypeAsync(Text, new TypeOptions { Delay = _Ran_.Next(15, 25) });
                    return true;
                }
            }
            catch (Exception)
            {
                // ignored
            }

            return false;
        }

        public static async Task<bool> MyInputAndFocus(this IElementHandle El, string Text)
        {
            try
            {
                if (El != null)
                {
                    await El.FocusAsync();
                    await TaskHelper.Delay(_Ran_.Next(200, 400));
                    await El.TypeAsync(Text, new TypeOptions { Delay = _Ran_.Next(15, 25) });
                    return true;
                }
            }
            catch (Exception)
            {
            }

            return false;
        }

        public static async Task<string> MyTextContent(this IElementHandle El)
        {
            try
            {
                return await El.MyAttrSting("textContent");
            }
            catch (Exception)
            {
            }

            return null;
        }

        public static async Task<string> MyAttrSting(this IElementHandle El, string Attr)
        {
            try
            {
                var textContent = await (await El.GetPropertyAsync(Attr)).JsonValueAsync() as string;
                return textContent;
            }
            catch (Exception)
            {
            }

            return null;
        }

        /// <summary>
        ///     元素滚动 默认等待1秒
        /// </summary>
        /// <param name="El"></param>
        /// <returns></returns>
        public static async Task MyScrollIntoView(this IElementHandle El)
        {
            try
            {
                await El.EvaluateFunctionAsync(@"(element)=>{
element.scrollIntoView();
}", El);
                await TaskHelper.Delay(1000);
            }
            catch (Exception)
            {
            }
        }

        /// <summary>
        ///     React节点取属性返回Json文本   React.+node
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public static async Task<string> MyReact(this IPage p, string Selector, string node)
        {
            var Script = Script_React;
            Script += "JSON.stringify(cefjs_React(document.querySelector(\"" + Selector + "\"))." + node + ")";
            return await p.EvaluateExpressionAsync<string>(Script);
        }

        private static ClickOptions GetClickOptions()
        {
            var ClickOpt = new ClickOptions();
            ClickOpt.ClickCount = 1;
            ClickOpt.Button = MouseButton.Left;
            ClickOpt.Delay = _Ran_.Next(15, 30);
            //var El = await _PddPage.QuerySelectorAsync(Selector);
            //El.BoundingBoxAsync().Result.X
            //  ClickOpt.OffSet = new Offset(0, -_Ran_.Next(3));
            return ClickOpt;
        }

        public static async Task PaddEnhanceCookies(this IPage El)
        {
            try
            {
                //,\"pdd_user_uin\",\"pdd_user_id\",\"api_uid\",\"pdd_vds\"
                await El.EvaluateExpressionHandleAsync(
                    "function $MySetCookie(key, value, days) {\r\n  var date = new Date();\r\n  days = days || 365;\r\n  date.setTime(+date + days * 86400000); //24 * 60 * 60 * 1000\r\n  window.document.cookie =\r\n    key + \"=\" + value + \"; expires=\" + date.toUTCString() + \"; path=/; domain=mobile.yangkeduo.com\";\r\n  return value;\r\n}\r\nfunction $MyGetCookie(name) {\r\n  const value = `; ${document.cookie}`;\r\n  const parts = value.split(`; ${name}=`);\r\n  if (parts.length === 2) return parts.pop().split(\";\").shift();\r\n}\r\nfunction SetPaddCookies() {\r\n    var CookieNames=[\"PDDAccessToken\"];\r\n    CookieNames.forEach(element => {\r\n        var val= $MyGetCookie(element);\r\n        if (val!=undefined) {\r\n         $MySetCookie(element,val,365*20);\r\n        }\r\n    });\r\n  \r\n}\r\nSetPaddCookies();");
            }
            catch (Exception)
            {
            }
        }
    }
}